﻿using System;
using System.Linq;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using IntraVision.Web.Toolkit;

namespace IntraVision.Web
{
    public static class LINQHelper
    {
        public static bool IsIn(this int i, int[] ids)
        {
            return ids.Contains(i);
        }

        public static bool IsIn(this int i, string str)
        {
            int[] ids = RequestHelper.GetIdsFromString(str);
            if (ids == null || ids.Length == 0) return false;
            return ids.Contains(i);
        }

        public static T CopyObject<T>(this T original) where T : class, new()
        {
            T copy = new T();

            var props = (from prop in typeof(T).GetProperties() where prop.CanRead && prop.CanWrite && prop.GetCustomAttributes(typeof(AssociationAttribute), true).Length == 0 select prop);
            foreach (PropertyInfo pi in props)
            {
                ColumnAttribute dbcolumn = null;
                if (pi.GetCustomAttributes(typeof(ColumnAttribute), true).Length > 0)
                    dbcolumn = pi.GetCustomAttributes(typeof(ColumnAttribute), true)[0] as ColumnAttribute;
                //Dont copy db generated columns and primary keys
                if (dbcolumn != null && (dbcolumn.IsDbGenerated || dbcolumn.IsPrimaryKey)) continue;

                try { pi.SetValue(copy, pi.GetValue(original, null), null); }
                catch { }
            }
            return copy;
        }

        public static string GenerateSearchString(Type t)
        {
            var props = (from prop in t.GetProperties() where prop.CanRead && prop.PropertyType == typeof(string) && prop.GetCustomAttributes(typeof(ColumnAttribute), true).Length > 0 select prop);
            StringBuilder sb = new StringBuilder();
            foreach (PropertyInfo pi in props)
            {
                if (sb.Length > 0) sb.Append("||");
                sb.Append(pi.Name + ".Contains(@0)");
            }
            return sb.ToString();
        }

        public static string ParseFormat(string format, object entity)
        {
            if (string.IsNullOrEmpty(format) || format.IndexOf("{@") < 0) return format;

            Regex re = new Regex(@"{@(?<params>[^}|]+)(\|(?<options>[^}]+))?}");
            foreach (Match m in re.Matches(format))
            {
                string propName = m.Result("${params}");
                string[] options = null;
                if (!string.IsNullOrEmpty(m.Result("${options}")))
                    options = m.Result("${options}").Split('|');

                PropertyInfo prop = entity.GetType().GetProperty(propName);
                if (entity == null || prop == null)
                    format = format.Replace("{@" + propName + "}", "");
                else
                {
                    object val = prop.GetValue(entity, null);
                    string strval = Convert.ToString(val, CultureInfo.InvariantCulture);
                    if (options != null)
                    {
                        for (int i = 0; i < options.Length; i++)
                        {
                            string option = options[i];
                            string optkey = option;
                            string optval = "";
                            if (option.IndexOf('=') > 0)
                            {
                                optkey = option.Split('=')[0];
                                optval = option.Split('=')[1];
                            }
                            switch (optkey.ToUpperInvariant())
                            {
                                case "SMARTNUMBER":
                                    Decimal number = Convert.ToDecimal(val);
                                    if (number > 0) strval = number.FormatNumber();
                                    else strval = "";
                                    break;
                                case "SMARTDATE":
                                    DateTime smartdate = Convert.ToDateTime(val);
                                    if (smartdate == DateTime.MinValue) continue;
                                    string dtFormat = smartdate.Year == DateTime.Now.Year ? "dd MMMM" : "dd.MM.yyyy";
                                    if (smartdate.Hour > 0 || smartdate.Minute > 0) dtFormat += ", HH:mm";
                                    strval = smartdate.ToString(dtFormat);
                                    break;
                                case "REMAINDATE":
                                    DateTime remaindate = Convert.ToDateTime(val);
                                    if (remaindate == DateTime.MinValue) continue;
                                    if( DateTime.Now < remaindate && remaindate < DateTime.Now.AddHours(1))
                                        strval = String.Format("{0}мин", (remaindate - DateTime.Now).Minutes);
                                    else if (DateTime.Now < remaindate && remaindate < DateTime.Now.AddHours(24))
                                    {
                                        TimeSpan datediff = remaindate - DateTime.Now;
                                        strval = String.Format("{0}ч {1}мин", datediff.Hours, datediff.Minutes);
                                    }
                                    else
                                    {
                                        string remaindateFormat = "dd.MM.yyyy";
                                        if (remaindate.Hour > 0 || remaindate.Minute > 0) remaindateFormat += ", HH:mm";
                                        strval = remaindate.ToString(remaindateFormat);
                                    }
                                    break;
                                case "DATEFORMAT":
                                    DateTime date = Convert.ToDateTime(val);
                                    if (date == DateTime.MinValue) continue;
                                    strval = date.ToString(optval);
                                    break;
                                case "MAXLENGTH":
                                    int maxlength = Convert.ToInt32(optval, CultureInfo.InvariantCulture);
                                    if( strval.Length > maxlength) strval = strval.Substring(0, maxlength) + "...";
                                    break;
                                case "BOOL":
                                    bool flag = Convert.ToBoolean(val);
                                    strval = flag ? "Да" : "Нет";
                                    break;
                                case "NULLABLEBOOL":
                                    strval = (bool?) val == null ? "" : ((bool) val ? "Да" : "Нет");
                                    break;
                                case "HTML":
                                    strval = HTMLHelper.EscapeHTML(Convert.ToString(val, CultureInfo.InvariantCulture));
                                    break;
                            }
                        }
                    }
                    format = format.Replace(m.ToString(), strval);
                }
            }

            return format;
        }

        public static bool UpdateEntitySet<TEntity, TDataContext>(this EntitySet<TEntity> entities, string propertyName, int[] ids, TDataContext dc)
            where TDataContext : DataContext
            where TEntity : class, new()
        {
            return entities.UpdateEntitySet(propertyName, ids, dc, false);
        }

        public static bool UpdateEntitySet<TEntity,TDataContext>(this EntitySet<TEntity> entities, string propertyName, int[] ids, TDataContext dc, bool appendOnly)
            where TDataContext : DataContext
            where TEntity : class, new()
        {
            bool changed = false;
            if (ids.Length == 0 && entities.Any())
            {
                dc.GetTable<TEntity>().DeleteAllOnSubmit(entities);
                entities.Clear();
                changed = true;
            }
            else
            {
                var table = dc.GetTable<TEntity>();
                var prop = typeof(TEntity).GetProperty(propertyName);

                //Удалить те элементы, которых нет в списке
                if (!appendOnly)
                {
                    int pos = 0;
                    int len = entities.Count();
                    for (int i = 0; i < len; i++)
                    {
                        int id = (int)prop.GetValue(entities[pos], null);
                        if (!ids.Contains(id))
                        {
                            table.DeleteOnSubmit(entities[pos]);
                            entities.Remove(entities[pos]);
                            changed = true;
                        }
                        else pos++;
                    }
                }

                //Добавить новые элементы списка
                foreach (int id in ids)
                {
                    if (entities.Any(e => (int)prop.GetValue(e, null) == id)) continue;
                    var entity = new TEntity();
                    prop.SetValue(entity, id, null);
                    entities.Add(entity);
                    changed = true;
                }
            }
            return changed;
        }

        public static IOrderedQueryable<T> OrderBy_<T>(this IQueryable<T> source, string property)
        {
            return ApplyOrder<T>(source, property, "OrderBy");
        }
        public static IOrderedQueryable<T> OrderByDescending_<T>(this IQueryable<T> source, string property)
        {
            return ApplyOrder<T>(source, property, "OrderByDescending");
        }
        public static IOrderedQueryable<T> ThenBy_<T>(this IOrderedQueryable<T> source, string property)
        {
            return ApplyOrder<T>(source, property, "ThenBy");
        }
        public static IOrderedQueryable<T> ThenByDescending_<T>(this IOrderedQueryable<T> source, string property)
        {
            return ApplyOrder<T>(source, property, "ThenByDescending");
        }
        static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName)
        {
            string[] props = property.Split('.');
            Type type = typeof(T);
            ParameterExpression arg = Expression.Parameter(type, "x");
            Expression expr = arg;
            foreach (string prop in props)
            {
                // use reflection (not ComponentModel) to mirror LINQ
                PropertyInfo pi = type.GetProperty(prop);
                expr = Expression.Property(expr, pi);
                type = pi.PropertyType;
            }
            Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
            LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

            object result = typeof(Queryable).GetMethods().Single(
                    method => method.Name == methodName
                            && method.IsGenericMethodDefinition
                            && method.GetGenericArguments().Length == 2
                            && method.GetParameters().Length == 2)
                    .MakeGenericMethod(typeof(T), type)
                    .Invoke(null, new object[] { source, lambda });
            return (IOrderedQueryable<T>)result;
        }
    }
}